#if defined (_WIN32) || defined(__WIN32__)
#include <Windows.h>
#else
#include <unistd.h>
#include <dlfcn.h>
#endif

#include <iostream>
#include <fstream>
#include <cstring>
#include <cstdlib>
#include <sstream>
#include <sys/types.h>
#include <sys/stat.h>

#include "mi.hpp"
#include "hibakodok.hpp"

using namespace std;

#if defined (_WIN32) || defined(__WIN32__)

std::wstring s2ws(const std::string& s) {
    int len;
    int slength = (int) s.length() + 1;
    len = MultiByteToWideChar(CP_ACP, 0, s.c_str(), slength, 0, 0);
    wchar_t* buf = new wchar_t[len];
    MultiByteToWideChar(CP_ACP, 0, s.c_str(), slength, buf, len);
    std::wstring r(buf);
    delete[] buf;
    return r;
}

#endif

MI::MI(string fn) {
    fajlNev = fn;
}

bool MI::betolt() {
    struct stat buf;
    if (stat(fajlNev.c_str(), &buf) == 0) {
        return true;
    }
    return false;
}

bool MI::lep(char **map, int mapX, int mapY, char self, int& x, int& y) {

    if (fajlNev.find(".so") || fajlNev.find(".dll")) {
        
        return MI::konyvtarLep(map, mapX, mapY, self, x, y);
    } else {
        return MI::programLep(map, mapX, mapY, self, x, y);
    }

}

bool MI::konyvtarLep(char **map, int mapX, int mapY, char self, int& x, int& y) {

#if defined (_WIN32) || defined(__WIN32__)

    // Windows library loading

    miFuggveny MI;

    HINSTANCE hinstLib = LoadLibrary((LPCWSTR) s2ws(fajlNev).c_str());
    if (hinstLib == NULL) {
        return false;
    }

    MI = (miFuggveny) GetProcAddress(hinstLib, "MI");
    if (MI == NULL) {
        FreeLibrary(hinstLib);
        return false;
    }

    MI(map, mapX, mapY, self, &x, &y);

    return true;

#else

    // Linux shared library loading

    void *lib_handle;
    char *error;
    lib_handle = dlopen(fajlNev.c_str(), RTLD_LAZY);
    if (!lib_handle) return 1;
    miFuggveny mi = (miFuggveny) dlsym(lib_handle, "mi");
    if ((error = dlerror()) != NULL) return false;
    mi(map, mapX, mapY, self, &x, &y);
    dlclose(lib_handle);
    return true;

#endif

}

bool MI::programLep(char **map, int mapX, int mapY, char self, int& x, int& y) {

#if defined (_WIN32) || defined(__WIN32__)

    // Windows program loading
    return false;
#else

    // Linux program loading

    int readPipe[2];
    int writePipe[2];

    pipe(readPipe);
    pipe(writePipe);

    int pid = fork();

    if (pid == -1) {
        perror("fork");
        throw MI_FORK_HIBA;
    } else if (pid == 0) {
        // Gyerek folyamatban vagyunk

        // Bezárjuk az "írási" pipe író felét
        // (irási, mert az "anya" ebbe ír, tehát mi olvassuk)
        close(writePipe[1]);
        // Bezárjuk az olvasási pipe olvasó felét
        // (olvasási, mert az anya abból olvas, tehát mi írunk bele)
        close(readPipe[0]);

        // Beállítjuk az írási pipe kimenetét (olvasó felét) stdin-nek
        dup2(writePipe[0], 0);
        // és az olvasási pipe bementetét (író felét) stdout-nak
        dup2(readPipe[1], 1);

        // Majd a pipe descriptorokat bezárjuk
        // (most már stdin és stdout mutat rájuk)
        close(writePipe[0]);
        close(readPipe[1]);

        // Előkészítjük a programot és futtatjuk
        char *argv[2];
        argv[0] = strdup(fajlNev.c_str());
        argv[1] = (char*) 0;
        execv(argv[0], argv);

        // Ha probléma lenne, az execv visszatér
        perror("exec");
        throw MI_EXEC_HIBA;
    } else {
        // Szülőben vagyunk

        // Bezárjuk a csövek azon felét ami a gyerek fele van
        // (az írási pipe olvasási felét és fordítva)
        close(writePipe[0]);
        close(readPipe[1]);

        // Amit a programnak írunk, azt egy stringstream tárolja ideiglenesen
        // (így egyszerűbb összepakolni a goldokat neki :P)
        stringstream ss;

        // Kiírjuk a kezdő adatokat
        // sorok/y mezok/x karakter
        ss << mapY << " " << mapX << " " << self << " ";

        // Kiírjuk a pályát
        for (int j = 0; j < mapY; j++) {
            for (int i = 0; i < mapX; i++) {
                ss << map[i][j] << " ";
            }
        }
        ss << "\n";

        char *str = strdup(ss.str().c_str());
        write(writePipe[1], str, strlen(str) + 1);
        close(writePipe[1]);

        char buffer[32];

        if (read(readPipe[0], buffer, sizeof (buffer)) == 0) {
            return false;
        } else {
            close(readPipe[0]);
            stringstream rss;
            rss << buffer;
            rss >> x;
            rss >> y;
        }

        return true;

    }

#endif

}
